////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2002.
// -------------------------------------------------------------------------
//  File name:   ParticleSubEmitter.cpp
//  Created:     20/04/2010 by Corey
//  Description: Split out from ParticleEmitter.cpp
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "ParticleSubEmitter.h"
#include "ParticleEmitter.h"
#include "ParticleContainer.h"
#include "Particle.h"

#include "ICryAnimation.h"			// CharInstance ref

#include <SpuUtils.h>

//////////////////////////////////////////////////////////////////////////
// Misc functions.

inline float round_up(float f, float r)
{
	if (f >= 0.f && r > 0.f)
	{
		float m = fmod(f, r);
		f += r-m;
	}
	return f;
}

//////////////////////////////////////////////////////////////////////////
// CParticleSource implementation.
//////////////////////////////////////////////////////////////////////////

CParticleSource::CParticleSource()
	: m_pEmitter(NULL)
{
}

CParticleSource::~CParticleSource()
{
	// Free geom refs.
	if (m_EmitGeom.m_pStatObj)
		m_EmitGeom.m_pStatObj->Release();
	if (m_EmitGeom.m_pChar)
		m_EmitGeom.m_pChar->Release();
	if (m_EmitGeom.m_pPhysEnt)
		m_EmitGeom.m_pPhysEnt->Release();
}

SPU_NO_INLINE bool CParticleSource::SetEmitGeom( GeomRef geom )
{
	if (geom.m_pStatObj == m_EmitGeom.m_pStatObj
	 && geom.m_pChar == m_EmitGeom.m_pChar
	 && geom.m_pPhysEnt == m_EmitGeom.m_pPhysEnt)
	 return false;

	if (m_EmitGeom.m_pStatObj)
		m_EmitGeom.m_pStatObj->Release();
	if (m_EmitGeom.m_pChar)
		m_EmitGeom.m_pChar->Release();
	if (m_EmitGeom.m_pPhysEnt)
		m_EmitGeom.m_pPhysEnt->Release();

	m_EmitGeom = geom;

	if (m_EmitGeom.m_pStatObj)
		m_EmitGeom.m_pStatObj->AddRef();
	if (m_EmitGeom.m_pChar)
		m_EmitGeom.m_pChar->AddRef();
	if (m_EmitGeom.m_pPhysEnt)
		m_EmitGeom.m_pPhysEnt->AddRef();

	return true;
}

//////////////////////////////////////////////////////////////////////////
// CParticleSubEmitter implementation.
//////////////////////////////////////////////////////////////////////////

CParticleSubEmitter::CParticleSubEmitter( CParticleSubEmitter* pParent, CParticleSource* pSource, CParticleContainer* pCont )
	: m_ChaosKey(0U)
	, m_nEmitIndex(0)
	, m_nSequenceIndex(0)
	, m_fSoundRangeSq(0.f)
	, m_pContainer(pCont)
	, m_pSource(pSource)
	, m_pParentEmitter(pParent)
{
	assert(pCont);
	assert(pSource);
	SetLastLoc();
	m_fStartAge = m_fEndAge = m_fRepeatAge = fHUGE;
	m_fLastUpdateAge = fHUGE;
	m_fNextEmitAge = 0.f;
	m_pForce = NULL;
	m_SoundID = INVALID_SOUNDID;
	m_bOneShotSoundGotStarted = false;
}

CParticleSubEmitter::~CParticleSubEmitter()
{
	if (m_SoundID != INVALID_SOUNDID)
	{
		// Use smart pointer when working with local copies.
		_smart_ptr<ISound> pSound(gEnv->pSoundSystem->GetSound(m_SoundID));
		if (pSound && (pSound->GetFlags() & FLAG_SOUND_LOOP))
		{
			pSound->Stop();
		}
	}
	if (m_pForce)
	{
		GetPhysicalWorld()->DestroyPhysicalEntity(m_pForce);
	}
}

//////////////////////////////////////////////////////////////////////////
SPU_NO_INLINE void CParticleSubEmitter::Start( float fAge )
{
	const ResourceParticleParams& params = GetParams();

	m_nEmitIndex = 0;
	m_nSequenceIndex = m_pContainer->GetNextEmitterSequence();
	SetLastLoc();
	
	m_bOneShotSoundGotStarted = false;

	// Reseed randomness.
	m_ChaosKey = CChaosKey(cry_rand32());
	m_fNextEmitAge = fAge;

	// Compute next repeat age.
	m_fRepeatAge = fHUGE;
	if (params.fPulsePeriod.GetMaxValue() > 0.f)
	{
		float fRepeat = params.fPulsePeriod.GetVarValue();
		fRepeat = max(fRepeat, 0.1f);
		if (fAge + fRepeat < GetAge())
		{
			// Needs to repeat again. Calculate final repeat before present, adjust.
			float fAdjust = GetAge() - fAge;
			fAdjust -= fmod(fAdjust, fRepeat);
			fAge += fAdjust;
		}
		m_fRepeatAge = fAge + fRepeat;
	}
	else if (params.fPulsePeriod.GetMaxValue() < 0.f && m_pParentEmitter)
	{
		// Inherit from parent.
		m_fRepeatAge = m_pParentEmitter->m_fRepeatAge;
	}

	// Compute lifetime params.
	m_fStartAge = m_fEndAge = fAge + params.fSpawnDelay.GetVarValue();
	if (params.bContinuous)
	{
		if (params.fEmitterLifeTime)
			m_fEndAge += params.fEmitterLifeTime.GetVarValue();
		else
			m_fEndAge = fHUGE;
	}
}

SPU_NO_INLINE void CParticleSubEmitter::UpdateState()
{
	// Evolve emitter state.
	float fAge = GetAge();

	if (fAge < m_fLastUpdateAge)
		// Reset state when particle source recycled.
		m_fStartAge = m_fEndAge = m_fRepeatAge = fHUGE;

	if (fAge >= m_fRepeatAge && GetSource().GetEndAge() > m_fRepeatAge)
		Start(m_fRepeatAge);
	else if (fAge < m_fStartAge)
	{
		float fActivateAge = GetParams().eSpawnIndirection == ParticleSpawn_ParentCollide ? GetSource().GetCollideAge() 
			: GetParams().eSpawnIndirection == ParticleSpawn_ParentDeath ? GetSource().GetEndAge() 
			: 0.f;
		if (fAge >= fActivateAge)
			Start(fActivateAge);
	}
	if (GetSource().GetEndAge() < m_fEndAge)
		m_fEndAge = GetSource().GetEndAge();
	m_fLastUpdateAge = fAge;
}

float CParticleSubEmitter::GetEndAge( ESoundControlTime eControl ) const
{
	switch (eControl)
	{
		default:
		case SoundControlTime_EmitterLifeTime:
			return GetEndAge();
		case SoundControlTime_EmitterExtendedLifeTime:
			return GetParticleEndAge();
		case SoundControlTime_EmitterPulsePeriod:
			return m_fRepeatAge;
	}
}

float CParticleSubEmitter::GetAgeRelativeTo( float fEndAge, float fAgeAdjust ) const
{
	float fAge = GetAge() + fAgeAdjust;
	if (min(fAge, fEndAge) <= m_fStartAge)
		return 0.f;

	return div_min(fAge - m_fStartAge, fEndAge - m_fStartAge, 1.f);
}

SPU_NO_INLINE float CParticleSubEmitter::GetStrength( float fAgeAdjust /* = 0.f */, ESoundControlTime const eControl /* = SoundControlTime_EmitterLifeTime */ ) const
{
	float fStrength = GetMain().GetSpawnParams().fStrength;
	if (fStrength < 0.f)
	{
		if (GetParams().bContinuous)
			return GetRelativeAge( eControl, fAgeAdjust );
		else
			return (float)m_nEmitIndex / GetParams().fCount.GetMaxValue();
	}
	else
		return min(fStrength, 1.f);
}

SPU_NO_INLINE void CParticleSubEmitter::EmitParticles( SParticleUpdateContext const& context )
{
	const ResourceParticleParams& params = GetParams();

	float fAge = GetAge();
	if (fAge >= m_fStartAge && (params.bContinuous || fAge >= m_fNextEmitAge))
	{
		const float fStrength = GetStrength();
		float fCount = params.fCount.GetVarValue(fStrength);
		if (!GetContainer().GetParent())
			fCount *= GetMain().GetEmitCountScale();
		if (params.Connection.bConnectToOrigin)
			fCount -= 1.f;
		if (fCount <= 0.f)
			return;

		const float fParticleLife = params.GetMaxParticleLife();
		if (fParticleLife <= 0.f)
			return;

		if (params.bContinuous)
		{
			// Continuous emission rate which maintains fCount particles.
			const float fEmitterLife = params.GetMaxEmitterLife();
			float fEmitRate = max(fCount / fParticleLife, (fCount - 1.f) / fEmitterLife);
			float fAgeIncrement = 1.f / fEmitRate;

			// Determine time window to update.
			float fAge0 = max(m_fNextEmitAge, m_fStartAge);
			const float fFullLife = GetContainer().GetMaxParticleFullLife();
			if (fAge > fAge0 + fFullLife)
			{
				// Skip time before emitted particles would still be alive.
				int nSkip = int_ceil((fAge - fAge0 - fFullLife) * fEmitRate);
				fAge0 += nSkip * fAgeIncrement;
			}
			float fAge1 = min(fAge, GetEndAge());

			if (params.Connection.bConnectToOrigin)
				fAge -= fAgeIncrement;

			while (fAge0 < fAge1)
			{
				if (!EmitParticle(context, fAge - fAge0))
				{
					GetContainer().GetCounts().ParticlesReject += (fAge - fAge0) * fEmitRate;
					break;
				}
				fAge0 += fAgeIncrement;
			}
			m_fNextEmitAge = fAge0;
		}
		else
		{
			// Emit only once, if still valid.
			// Always emit first frame, even if lifetime < frame time.				
			if (fAge <= m_fStartAge + fParticleLife + GetParticleTimer()->GetFrameTime()*2)
			{
				for (int nEmit = int_round(fCount); nEmit > 0; nEmit--)
				{
					if (!EmitParticle(context, fAge - m_fStartAge))
					{
						GetContainer().GetCounts().ParticlesReject += nEmit;
						break;
					}
				}
				m_fNextEmitAge = fHUGE;
			}
		}
	}
}

SPU_NO_INLINE bool CParticleSubEmitter::GetMoveRelative( QuatTS& qtMove ) const
{
	const QuatTS& qpCur = GetSource().GetLocation();
	if (m_qpLastLoc.s > 0.f || qpCur.s == m_qpLastLoc.s)
	{
		qtMove.q = m_qpLastLoc.q.GetInverted() * qpCur.q;
		qtMove.s = qpCur.s / m_qpLastLoc.s;
		qtMove.t = qtMove.q * (-qtMove.s * m_qpLastLoc.t) + qpCur.t;
		return true;
	}
	return false;
}

SPU_NO_INLINE bool CParticleSubEmitter::GetLocalTarget( ParticleTarget& target ) const
{
	// Local target from parent emitter.
	for (const CParticleSubEmitter* pParent = GetParent(); pParent; pParent = pParent->GetParent())
	{
		if (pParent->GetParams().eForceGeneration == ParticleForce_Target)
		{
			target.bTarget = true;
			target.vTarget = pParent->GetEmitPos();
			target.fRadius = pParent->GetParams().vRandomOffset.GetLength();
			target.vVelocity = pParent->GetSource().GetVelocity().vLin;
			return true;
		}
	}
	return false;
}

#ifndef __SPU__

void CParticleSubEmitter::UpdateForce()
{
	FUNCTION_PROFILER_SYS(PARTICLE);

	// Set or clear physical force.

	float fAge = GetAge();
	if (fAge >= m_fStartAge && fAge <= GetParticleEndAge())
	{
		struct SForceGeom
		{
			QuatTS	qpLoc;							// world placement of force
			AABB		bbOuter, bbInner;		// local boundaries of force.
			Vec3		vForce3;
			float		fForceW;
		} force;

		//
		// Compute force geom.
		//

		const ResourceParticleParams& params = GetParams();

		float fStrength = GetStrength();
		SPhysEnviron const& PhysEnv = GetMain().GetPhysEnviron();

		// Location.
		force.qpLoc = GetSource().GetLocation();
		force.qpLoc.s = GetMain().GetParticleScale();

		// Direction.
		if (params.bFocusGravityDir)
		{
			float fLenSq = PhysEnv.m_UniformForces.vAccel.GetLengthSquared();
			if (fLenSq > FLT_MIN)
				force.qpLoc.q = Quat::CreateRotationV0V1( Vec3(0,1,0), PhysEnv.m_UniformForces.vAccel * -isqrt_tpl(fLenSq) );
		}
		else if (params.fFocusAngle)
		{
			float fAngle = DEG2RAD(params.fFocusAngle.GetVarValue(fStrength, m_ChaosKey));
			float fAzimuth = DEG2RAD(params.fFocusAzimuth.GetVarValue(fStrength, m_ChaosKey));
			force.qpLoc.q = force.qpLoc.q * Quat::CreateRotationXYZ( Ang3(fAngle, fAzimuth, 0.f) );
		}

		force.qpLoc.t = force.qpLoc * params.vPositionOffset;

		// Set inner box from spawn geometry.
		Quat qToLocal = force.qpLoc.q.GetInverted() * m_pSource->GetLocation().q;
		Vec3 vOffset = qToLocal * Vec3(params.vRandomOffset);
		force.bbInner = AABB(-vOffset, vOffset);

		// Emission directions.
		float fPhiMax = DEG2RAD(params.fEmitAngle.GetVarValue(fStrength, 0.f)),
					fPhiMin = DEG2RAD(params.fEmitAngle.GetVarValue(fStrength, 1.f));

		AABB bbTrav;
		bbTrav.max.y = cosf(fPhiMin);
		bbTrav.min.y = cosf(fPhiMax);
		float fCosAvg = (bbTrav.max.y + bbTrav.min.y) * 0.5f;
		bbTrav.max.x = bbTrav.max.z = (bbTrav.min.y * bbTrav.max.y < 0.f ? 1.f : sin(fPhiMax));
		bbTrav.min.x = bbTrav.min.z = -bbTrav.max.x;
		bbTrav.Add(Vec3(ZERO));

		// Force magnitude: speed times relative particle density.
		float fPLife = params.fParticleLifeTime.GetVarValue(fStrength, 0.5f);
		float fTime = fAge-m_fStartAge;

		float fSpeed = params.fSpeed.GetVarValue(fStrength, 0.5f) * GetMain().GetSpawnParams().fSpeedScale;
		float fDist = TravelDistance( abs(fSpeed), params.fAirResistance.GetVarValue(fStrength, 0.5f), min(fTime,fPLife) );
		fSpeed = TravelSpeed( abs(fSpeed), params.fAirResistance.GetVarValue(fStrength, 0.5f), min(fTime,fPLife) );
		float fForce = fSpeed * params.fAlpha.GetVarValue(fStrength, 0.5f) * force.qpLoc.s;

		if (params.bContinuous && fPLife > 0.f)
		{
			// Ramp up/down over particle life.
			float fEndAge = GetEndAge();
			if (fTime < fPLife)
				fForce *= fTime/fPLife;
			else if (fTime > fEndAge)
				fForce *= 1.f - (fTime-fEndAge) / fPLife;
		}

		// Force direction.
		force.vForce3.zero();
		force.vForce3.y = fCosAvg * fForce;
		force.fForceW = sqrtf(1.f - square(fCosAvg)) * fForce;

		// Travel distance.
		bbTrav.min *= fDist;
		bbTrav.max *= fDist;

		// Set outer box.
		force.bbOuter = force.bbInner;
		force.bbOuter.Augment(bbTrav);

		// Expand by size.
		float fSize = params.fSize.GetVarValue(fStrength, 0.5f);
		force.bbOuter.Expand( Vec3(fSize) );

		// Scale: Normalise box size, so we can handle some geom changes through scaling.
		Vec3 vSize = force.bbOuter.GetSize()*0.5f;
		float fRadius = max(max(vSize.x, vSize.y), vSize.z);

		if (fForce * fRadius == 0.f)
		{
			// No force.
			if (m_pForce)
			{
				GetPhysicalWorld()->DestroyPhysicalEntity(m_pForce);
				m_pForce = NULL;
			}
			return;
		}

		force.qpLoc.s *= fRadius;
		float fIRadius = 1.f / fRadius;
		force.bbOuter.min *= fIRadius;
		force.bbOuter.max *= fIRadius;
		force.bbInner.min *= fIRadius;
		force.bbInner.max *= fIRadius;

		//
		// Create physical area for force.
		//

		primitives::box geomBox;
		geomBox.Basis.SetIdentity();
		geomBox.bOriented = 0;
		geomBox.center = force.bbOuter.GetCenter();
		geomBox.size = force.bbOuter.GetSize() * 0.5f;

		pe_status_pos spos;
		if (m_pForce)
		{
			// Check whether shape changed.
			m_pForce->GetStatus(&spos);
			if (spos.pGeom)
			{
				primitives::box curBox;
				spos.pGeom->GetBBox(&curBox);
				if (!curBox.center.IsEquivalent(geomBox.center, 0.001f)
				 || !curBox.size.IsEquivalent(geomBox.size, 0.001f))
				 spos.pGeom = NULL;
			}
			if (!spos.pGeom)
			{
				GetPhysicalWorld()->DestroyPhysicalEntity(m_pForce);
				m_pForce = NULL;
			}
		}

		if (!m_pForce)
		{
			IGeometry *pGeom = m_pPhysicalWorld->GetGeomManager()->CreatePrimitive( primitives::box::type, &geomBox );
			m_pForce = m_pPhysicalWorld->AddArea( pGeom, force.qpLoc.t, force.qpLoc.q, force.qpLoc.s );
			if (!m_pForce)
				return;

			// Tag area with this emitter, so we can ignore it in the emitter family.
			pe_params_foreign_data fd;
			fd.pForeignData = (void*)&GetMain();
			fd.iForeignData = fd.iForeignFlags = 0;
			m_pForce->SetParams(&fd);
		}
		else
		{
			// Update position & box size as needed.
			if (!spos.pos.IsEquivalent(force.qpLoc.t, 0.01f)
				|| !Quat::IsEquivalent(spos.q, force.qpLoc.q)
				|| spos.scale != force.qpLoc.s)
			{
				pe_params_pos pos;
				pos.pos = force.qpLoc.t;
				pos.q = force.qpLoc.q;
				pos.scale = force.qpLoc.s;
				m_pForce->SetParams(&pos);
			}
		}

		// To do: 4D flow
		pe_params_area area;
		float fVMagSqr = force.vForce3.GetLengthSquared(),
					fWMagSqr = square(force.fForceW);
		float fMag = sqrtf(fVMagSqr + fWMagSqr);
		area.bUniform = (fVMagSqr > fWMagSqr) * 2;
		if (area.bUniform)
		{
			force.vForce3 *= fMag * isqrt_tpl(fVMagSqr);
		}
		else
		{
			force.vForce3.z = fMag * (force.fForceW < 0.f ? -1.f : 1.f);
			force.vForce3.x = force.vForce3.y = 0.f;
		}
		area.falloff0 = force.bbInner.GetRadius();
		area.size.x = max( abs(force.bbOuter.min.x), abs(force.bbOuter.max.x) );
		area.size.y = max( abs(force.bbOuter.min.y), abs(force.bbOuter.max.y) );
		area.size.z = max( abs(force.bbOuter.min.z), abs(force.bbOuter.max.z) );

		if (params.eForceGeneration == ParticleForce_Gravity)
			area.gravity = force.vForce3;
		m_pForce->SetParams(&area);

		if (params.eForceGeneration == ParticleForce_Wind)
		{
			pe_params_buoyancy buoy;
			buoy.iMedium = 1;
			buoy.waterDensity = buoy.waterResistance = 1.f;
			buoy.waterFlow = force.vForce3;
			buoy.waterPlane.n = -PhysEnv.m_UniformForces.plWater.n;
			buoy.waterPlane.origin = PhysEnv.m_UniformForces.plWater.n * PhysEnv.m_UniformForces.plWater.d;
			m_pForce->SetParams(&buoy);
		}
	}
	else
	{
		if (m_pForce)
		{
			GetPhysicalWorld()->DestroyPhysicalEntity(m_pForce);
			m_pForce = NULL;
		}
	}
}

void CParticleSubEmitter::UpdateSound()
{
  FUNCTION_PROFILER_SYS(PARTICLE);

	const ResourceParticleParams& params = GetParams();

	bool bStopSound = false;
	bool bStartSound = false;

	// Do we need to stop any sounds any sound?
	IF (!GetCVars()->e_Particles,0)
	{
		bStopSound = true;
	}
	else
	{
		if (m_SoundID == INVALID_SOUNDID && !params.sSound.empty())
		{
			float fAge = GetAge();
			if (fAge >= m_fStartAge)
			{
				float fEndAgeLoop = GetEndAge(),
				fEndAgeTransient = m_fStartAge + 0.25f;

				if (fAge < max(fEndAgeLoop, fEndAgeTransient))
					bStartSound = true;
			}
		}
	}

	// Use smart pointer when working with local copies.
	_smart_ptr<ISound> pSound;
	bool bSoundGotCreated = false;

	if (bStartSound)
	{
		// Get some Culling Info
		Vec3 vSoundPos = GetEmitPos();
		unsigned int nSoundFlags = FLAG_SOUND_DEFAULT_3D;
		SSoundCacheInfo CullInfo;
		ESoundSystemErrorCode nErrorCode = gEnv->pSoundSystem->GetCullingByCache(params.crcSoundName.get(), vSoundPos, CullInfo);

		// no need to even create the sound
		if (nErrorCode == eSoundSystemErrorCode_None)
		{
			// stop any sound which is playing before
			if (m_SoundID != INVALID_SOUNDID && !CullInfo.bLooping)
			{
				pSound = gEnv->pSoundSystem->GetSound(m_SoundID);

				if (pSound)
				{
					pSound->Stop();
					pSound = NULL;
					m_SoundID = INVALID_SOUNDID;
				}
			}

			m_fSoundRangeSq = CullInfo.fMaxRadiusSq + 50.f; // add a little pre-cache radius

			if (CullInfo.bCanBeCulled)
				return;

			// also exit if we have far looping sounds or oneshots which wants to play again because their sounds were stolen
			if (!CullInfo.b2D && m_fSoundRangeSq > 0.0f && !SoundInRange())
				return;

			if (!CullInfo.bLooping && m_bOneShotSoundGotStarted)
				return;
		}

		if (nErrorCode == eSoundSystemErrorCode_SoundCRCNotFoundInCache)
			nSoundFlags |= FLAG_SOUND_ADD_TO_CACHE;

		char sFixSoundName[256];
		gEnv->pSoundSystem->HackFixupName(params.sSound.c_str(), sFixSoundName, 256);

		pSound = gEnv->pSoundSystem->CreateSound( sFixSoundName, nSoundFlags );

		if (pSound)
		{
			bSoundGotCreated = true;
			m_SoundID = pSound->GetId();
			pSound->SetSemantic(eSoundSemantic_Particle);
			pSound->SetPosition( vSoundPos );

			if (params.fSoundFXParam.GetMinValue() < 1.f)
			{
				pSound->SetParam("particlefx", 1.f - params.fSoundFXParam.GetVarValue(GetStrength(0.0f, params.eSoundControlTime), m_ChaosKey), false);
			}

			pSound->Play();
			m_bOneShotSoundGotStarted = true;
		}
	}

	// Update Sound
	if (m_SoundID != INVALID_SOUNDID)
	{
		if (!pSound)
			pSound = gEnv->pSoundSystem->GetSound(m_SoundID);

		if (pSound)
		{
			// Update all sounds (looping + one-shot)
			if (!bSoundGotCreated)
			{
				pSound->SetPosition( GetEmitPos() );

				if (!params.fSoundFXParam.IsConstant())
				{
					// Update SoundFX modifier
					pSound->SetParam("particlefx", 1.f - params.fSoundFXParam.GetVarValue(GetStrength(0.0f, params.eSoundControlTime), m_ChaosKey), false);
				}
			}

			// Stop a looping sound on its desired stop-time.
			if (params.eSoundControlTime != SoundControlTime_EmitterPulsePeriod && pSound->IsInitialized() && (pSound->GetFlags() & FLAG_SOUND_LOOP) )
			{
				if (GetAge() >= GetEndAge(params.eSoundControlTime))
					bStopSound = true;
			}

			// also exit if we have far looping sounds
			float fSoundRange = pSound->GetMaxDistance() * pSound->GetDistanceMultiplier();
			m_fSoundRangeSq = (fSoundRange * fSoundRange) + 50.f; // add a little pre-cache radius

			if (m_fSoundRangeSq > 0.0f && !SoundInRange())
				bStopSound = true;

			if (bStopSound)
			{
				pSound->Stop();
				pSound = NULL;
				m_SoundID = INVALID_SOUNDID;
				m_bOneShotSoundGotStarted = false;
			}
		}
		else
		{
			// SoundID is invalid
			m_SoundID = INVALID_SOUNDID;
		}
	}
}
#endif

SPU_NO_INLINE int CParticleSubEmitter::EmitParticle(SParticleUpdateContext const& context, float fAge, const EmitParticleData* pData)
{
	const ResourceParticleParams& params = GetParams();

	EmitParticleData data;
	if (pData)
		data = *pData;

	if (!data.bHasLocation)
	{
		// Use emitter location, interpolate if emitter moved.
		// Leave data.bHasLocation false, for CParticle.Init().
		data.Location = GetSource().GetLocation();
		if (m_qpLastLoc.s >= 0.f && context.fUpdateTime > 0.f)
		{
			if (params.eMoveRelEmitter)
			{
				data.Location = m_qpLastLoc;
			}
			else if (fAge > 0.f)
			{
				// Interpolate emission position.
				const PivotLocation& locA = GetPivotLocation();
				const PivotLocation& locB = m_qpLastLoc;
				float fInterp = div_min(fAge, context.fUpdateTime, 1.f);

				// Interpolate rotation, scale, and relative pivot
				data.Location.q.SetNlerp(locA.q, locB.q, fInterp);
				data.Location.s = locA.s + (locB.s - locA.s) * fInterp;
				if (GetCVars()->e_ParticlesDebug & AlphaBit('p'))
					data.Location.t .SetLerp(locA.t, locB.t, fInterp);
				else
				{
					Vec3 vPivot; vPivot.SetLerp(locA.pivot, locB.pivot, fInterp);

					// Interpolate world pivot separately, use it to construct final position
					Vec3 vWorldPivot; vWorldPivot.SetLerp(locA.WorldPivot(), locB.WorldPivot(), fInterp);
					data.Location.t = vWorldPivot - data.Location.q * vPivot * data.Location.s;
				}
			}
		}
	}

	data.Location.s = GetMain().GetParticleScale();

	if (!data.pStatObj && !data.pPhysEnt && params.pStatObj != 0 && params.pStatObj->GetSubObjectCount())
	{
		// If bGeomInPieces, emit one particle per piece.
		// Else iterate in 2 passes to count pieces, and emit a random piece.
		int nPiece = -1;
		IStatObj* pStatObj = 0;
		while (!pStatObj)
		{
			int nPieces = 0;
			for (int i = params.GetSubGeometryCount()-1; i >= 0; i--)
			{
				IStatObj::SSubObject* pSub = SPU_MAIN_PTR(params.GetSubGeometry(i));
				if (pSub)
				{
					if (params.bGeometryInPieces)
					{
						EmitParticleData sub_data = data;
						sub_data.Location = data.Location * QuatTS(pSub->localTM);
						sub_data.pStatObj = pSub->pStatObj;
						if (!EmitParticle( context, fAge, &sub_data))
							continue;
					}
					else if (nPieces == nPiece)
					{
						pStatObj = pSub->pStatObj;
						break;
					}
					nPieces++;
				}
			}
			if (params.bGeometryInPieces)
				return nPieces;
			if (nPieces <= 1)
				break;
			nPiece = Random(nPieces);
		}
	}

	CParticle* pPart = GetContainer().AddParticle(this);
	if (pPart)
	{
		m_nEmitIndex++;
















		pPart->Init( context, fAge, this, data );


		return 1;
	}

	return 0;
}

//////////////////////////////////////////////////////////////////////////
bool CParticleSubEmitter::SoundInRange() const
{
	const Vec3 vSoundPos = GetEmitPos();

	// if we are calling this sound from the Particle tab in Database view, never cull by distance
	if (vSoundPos.IsZero(1.0f))
		return true;

	const Vec3 vCamPos = gEnv->pRenderer->GetCamera().GetPosition();
	const f32 fListenerRange = (vCamPos - vSoundPos).GetLengthSquared();
	const bool bInRange = fListenerRange <= m_fSoundRangeSq;
	return bInRange;
}

Vec3 CParticleSubEmitter::GetEmitPos() const
{ 
	return GetSource().GetWorldPosition(GetParams().vPositionOffset); 
}

Vec3 CParticleSubEmitter::GetLocalPivot() const
{
	if (!m_pContainer->GetParent())
		return GetMain().GetLocalPivot();
	else
		return Vec3(ZERO);
}

Vec3 CParticleSubEmitter::GetWorldPivot() const
{
	if (!m_pContainer->GetParent())
		return GetSource().GetLocation() * GetMain().GetLocalPivot();
	else
		return GetSource().GetLocation().t;
}

void CParticleSubEmitter::SetLastLoc()
{ 
	m_qpLastLoc = GetSource().GetLocation();
	if (!m_pContainer->GetParent())
		// Use main emitter pivot for direct effects.
		m_qpLastLoc.pivot = GetMain().GetLocalPivot();
}

